package com.fansl.allround.auth.config;

import com.fansl.allround.auth.handler.AllroundAuthenticationSuccessHandler;
import com.fansl.allround.auth.provider.sys.mobile.SysUserMobileAuthenticationFilter;
import com.fansl.allround.auth.provider.sys.mobile.SysUserMobileAuthenticationProvider;
import com.fansl.allround.auth.provider.sys.openid.SysUserOpenIdAuthenticationFilter;
import com.fansl.allround.auth.provider.sys.openid.SysUserOpenIdAuthenticationProvider;
import com.fansl.allround.auth.provider.ucenter.openid.OpenIdAuthenticationFilter;
import com.fansl.allround.auth.provider.ucenter.openid.OpenIdAuthenticationProvider;
import com.fansl.allround.auth.service.SysUserDetailsServiceImpl;
import com.fansl.allround.auth.service.UcenterUserDetailsServiceImpl;
import com.fansl.allround.auth.provider.social.SysSocialUserSignUp;
import com.fansl.allround.auth.provider.social.UcenterSocialUserSignUp;
import com.fansl.allround.auth.provider.social.qq.api.QQApi;
import com.fansl.allround.auth.provider.social.weibo.api.WeiboApi;
import com.fansl.allround.common.core.constant.SecurityConstants;
import com.fansl.allround.common.security.handler.AbstractAuthenticationFailureHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.Collections;

/**
 * @author fansl
 * @Description: 认证相关配置
 * @date 2019/7/16 20:25
 */
@Primary
@Order(90)
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private SysUserDetailsServiceImpl sysUserDetailsService;
    @Autowired
    private UcenterUserDetailsServiceImpl ucenterUserDetailsService;
    @Lazy
    @Autowired
    private AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;
    @Autowired
    private SysSocialUserSignUp sysSocialUserSignUp;
    @Autowired
    private UcenterSocialUserSignUp ucenterSocialUserSignUp;
    @Autowired
    private QQApi qqApi;
    @Autowired
    private WeiboApi weiboApi;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    @SneakyThrows
    protected void configure(HttpSecurity http) {
        http
                .addFilterAfter(sysUserMobileAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(sysUserOpenIdAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(openIdAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                //允许无权限访问的url
                .antMatchers("/actuator/**",
                        "/token/**", "/login/**", "/favicon.ico",
                        SecurityConstants.MOBILE_TOKEN_URL,
                        SecurityConstants.SOCIAL_TOKEN_URL,
                        SecurityConstants.OAUTH_TOKEN_URL,
                        SecurityConstants.UCENTER_SOCIAL_TOKEN_URL).permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsService(sysUserDetailsService);
        auth.authenticationProvider(provider);
    }

    @Bean
    @Override
    @SneakyThrows
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    @Bean
    public AuthenticationSuccessHandler loginSuccessHandler() {
        return AllroundAuthenticationSuccessHandler.builder()
                .objectMapper(objectMapper)
                .clientDetailsService(clientDetailsService)
                .passwordEncoder(passwordEncoder())
                .defaultAuthorizationServerTokenServices(defaultAuthorizationServerTokenServices).build();
    }

    @Bean
    public AuthenticationFailureHandler loginFailedHandler() {
        return new AbstractAuthenticationFailureHandler(objectMapper);
    }

    @Bean
    public SysUserMobileAuthenticationFilter sysUserMobileAuthenticationFilter() {
        SysUserMobileAuthenticationFilter filter = new SysUserMobileAuthenticationFilter();
        SysUserMobileAuthenticationProvider mobileAuthenticationProvider =
                new SysUserMobileAuthenticationProvider(sysUserDetailsService, redisTemplate);
        ProviderManager providerManager = new ProviderManager(Collections.singletonList(mobileAuthenticationProvider));
        filter.setAuthenticationManager(providerManager);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler());
        filter.setAuthenticationFailureHandler(loginFailedHandler());
        return filter;
    }

    @Bean
    public SysUserOpenIdAuthenticationFilter sysUserOpenIdAuthenticationFilter() {

        SysUserOpenIdAuthenticationProvider provider =
                new SysUserOpenIdAuthenticationProvider(sysUserDetailsService);
        ProviderManager providerManager = new
                ProviderManager(Collections.singletonList(provider));

        SysUserOpenIdAuthenticationFilter filter = new SysUserOpenIdAuthenticationFilter();
        filter.setQqApi(qqApi);
        filter.setWeiboApi(weiboApi);
        filter.setRedisTemplate(redisTemplate);
        filter.setAuthenticationManager(providerManager);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler());
        filter.setAuthenticationFailureHandler(loginFailedHandler());
        return filter;
    }

    @Bean
    public OpenIdAuthenticationFilter openIdAuthenticationFilter() {
        OpenIdAuthenticationProvider provider = new OpenIdAuthenticationProvider();
        provider.setUserDetailsService(ucenterUserDetailsService);
        provider.setSocialUserSignUp(ucenterSocialUserSignUp);
        ProviderManager providerManager =
                new ProviderManager(Collections.singletonList(provider));

        OpenIdAuthenticationFilter filter = new OpenIdAuthenticationFilter();
        filter.setQqApi(qqApi);
        filter.setWeiboApi(weiboApi);
        filter.setAuthenticationManager(providerManager);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler());
        filter.setAuthenticationFailureHandler(loginFailedHandler());
        return filter;
    }

    /**
     * https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
     * Encoded password does not look like BCrypt
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}
